package com.weilele.mvvm.view

import android.animation.ValueAnimator
import android.content.Context
import android.graphics.*
import android.graphics.drawable.BitmapDrawable
import android.util.AttributeSet
import android.view.MotionEvent
import com.weilele.mvvm.R
import com.weilele.mvvm.widget.BaseTextView
import kotlin.math.*


open class DashboardView : BaseTextView {
    companion object {
        private const val PAINT_CAP_ROUND = 1
        private const val PAINT_CAP_BUTT = 2
        private const val PAINT_CAP_SQUARE = 3
    }

    //是否在构造函数阶段
    private var isInint = true

    /**
     * dp2px
     */
    fun dip(value: Int): Int {
        return value * 3
    }

    /**
     * dp2px
     */
    fun dip(value: Float): Float {
        return value * 3
    }

    /***********************************支持自定义的属性start********************************/
    //圆环宽度
    var ringBackgroundWidth = dip(24f)
        set(value) {
            field = value
            if (!isInint) {
                invalidate()
            }
        }

    //当前进度圆环的宽度
    var ringWidth = dip(6f)
        set(value) {
            field = value
            if (!isInint) {
                invalidate()
            }
        }

    //圆环背景色
    var ringBackgroundColor = Color.BLACK
        set(value) {
            field = value
            if (!isInint) {
                invalidate()
            }
        }

    //当前进度的颜色-绿色
    var ringColor = intArrayOf(Color.GRAY, Color.GREEN)
        set(value) {
            field = value
            if (!isInint) {
                invalidate()
            }
        }

    //刻度线颜色
    var calibrationColor = Color.GRAY
        set(value) {
            field = value
            if (!isInint) {
                invalidate()
            }
        }

    //刻度线的宽度,默认短刻度线
    var calibrationWidth = dip(1f)
        set(value) {
            field = value
            if (!isInint) {
                invalidate()
            }
        }

    //刻度线的宽度,长刻度线，线宽
    var calibrationLongWidth = calibrationWidth
        set(value) {
            field = value
            if (!isInint) {
                invalidate()
            }
        }

    //刻度线的数量
    var calibrationCount = 20
        set(value) {
            field = value
            if (!isInint) {
                invalidate()
            }
        }

    //是否绘制第一条刻度线
    var isDrawFirstCalibration = false
        set(value) {
            field = value
            if (!isInint) {
                invalidate()
            }
        }

    //是否绘制最后一条刻度线
    var isDrawLastCalibration = false
        set(value) {
            field = value
            if (!isInint) {
                invalidate()
            }
        }

    //多少刻度线不绘制
    var calibrationUnDrawCount = 0
        set(value) {
            field = value
            if (!isInint) {
                invalidate()
            }
        }

    //长刻度线长度
    var calibrationLongLineWidth = dip(6f)
        set(value) {
            field = value
            if (!isInint) {
                invalidate()
            }
        }

    //短刻度线长度
    var calibrationShortLineWidth = dip(3f)
        set(value) {
            field = value
            if (!isInint) {
                invalidate()
            }
        }

    //刻度线距离边框的距离
    var calibrationMargin = 0f
        set(value) {
            field = value
            if (!isInint) {
                invalidate()
            }
        }

    //圆环距离边框的距离
    var ringMargin = 0f
        set(value) {
            field = value
            if (!isInint) {
                invalidate()
            }
        }


    //长刻度线对比短刻度线，多出来的部分是否向内
    var isCalibrationMoreLineInCircle = false
        set(value) {
            field = value
            if (!isInint) {
                invalidate()
            }
        }

    //多少个刻度画一个长的刻度线
    var calibrationLongSpace = 3
        set(value) {
            field = value
            if (!isInint) {
                invalidate()
            }
        }

    //仪表盘不需要绘制的角度 0-360f
    var unDrawAngle = 90f
        set(value) {
            field = value
            if (!isInint) {
                invalidate()
            }
        }

    //刻度线不需要绘制的角度 0-360f
    var calibrationUnDrawAngle = unDrawAngle
        set(value) {
            field = value
            if (!isInint) {
                invalidate()
            }
        }

    //圆环和刻度线画笔模式
    var ringCap = PAINT_CAP_ROUND
        set(value) {
            field = value
            when (value) {
                PAINT_CAP_BUTT -> {
                    paint.strokeCap = Paint.Cap.BUTT
                }
                PAINT_CAP_ROUND -> {
                    paint.strokeCap = Paint.Cap.ROUND
                }
                PAINT_CAP_SQUARE -> {
                    paint.strokeCap = Paint.Cap.SQUARE
                }
            }
            if (!isInint) {
                invalidate()
            }
        }

    //指针
    var pointerBitmap: Bitmap? = null
        set(value) {
            field = value
            if (!isInint) {
                invalidate()
            }
        }

    //是否动画改变进度
    var canAnimChangeProgress = true
        set(value) {
            field = value
            if (!isInint) {
                invalidate()
            }
        }

    //当前进度条末尾的小圆点
    var endPointColor = Color.RED
        set(value) {
            field = value
            if (!isInint) {
                invalidate()
            }
        }

    //当前进度条末尾的小圆点直径
    var endPointWidth = ringWidth
        set(value) {
            field = value
            if (!isInint) {
                invalidate()
            }
        }

    //当前进度值，最大值100,最小值0
    var currentProgress: Float = 45f
        set(value) {
            field = (360f - unDrawAngle) * min(value, 100f) / 100
            if (canAnimChangeProgress && !isInint) {
                progressAnim.cancel()
                progressAnim.setFloatValues(_currentProgress, field)
                progressAnim.start()
            } else {
                _currentProgress = field
                if (!isInint) {
                    invalidate()
                }
            }
        }
        get() {
            return field * 100 / (360f - unDrawAngle)
        }

    //当前进度比例，最大1f，最小0f
    var currentProgressRatio: Float
        set(value) {
            currentProgress = value.coerceAtMost(1f) * 100
        }
        get() {
            return currentProgress / 100
        }

    /**
     * 设置数据，最小点值，最大点值，当前值
     */
    fun setProgress(min: Float?, max: Float?, current: Float?, animChange: Boolean = true) {
        canAnimChangeProgress = animChange
        if (current == null || current == 0f) {
            currentProgressRatio = 0f
            return
        }
        val minValue = min ?: 0f
        val maxValue = max ?: 0f
        //每一个刻度，代表的值
        val off = (maxValue - minValue) / (calibrationCount)
        //总刻度值
        val allValuer = off * calibrationCount
        currentProgressRatio = if (allValuer == 0f) {
            0f
        } else {
            ((current - minValue)) / allValuer
        }
    }

    //滑动小圆点的的变化值，达到这个值，则视为本次滑动无效，避免一下进度变化太多，用户体验也好一点
    var allowSlidingMaxRatio = 0.2f

    //超过矩形多少距离，仍然认为在矩形内，允许手指滑动
    var fingerBeyondTheBorder = 24

    //拖拽小圆点回调，如果为null，小圆点无法拖拽
    private var onDragEndPointListener: Function2<Float, Boolean/*是否拖拽结束*/, Boolean/*是否允许本次滑动*/>? = null
    fun setOnDragEndPointListener(onDragEndPointListener: Function2<@ParameterName("newProgressRatio") Float, Boolean, Boolean>?) {
        this.onDragEndPointListener = onDragEndPointListener
    }

    /***********************************支持自定义的属性 end********************************/
    private val paint by lazy {
        Paint(Paint.ANTI_ALIAS_FLAG).apply {
            style = Paint.Style.STROKE
            strokeCap = Paint.Cap.ROUND
            isDither = true
        }
    }

    //开始画扇形的角度
    private val startAngle: Float
        get() = 90f + unDrawAngle / 2

    //当前进度值，一圈360f
    private var _currentProgress: Float = 270f

    private val progressAnim = ValueAnimator.ofFloat(0f, 0f).also { anim ->
        anim.setDuration(1000)
        anim.addUpdateListener {
            _currentProgress = it.animatedValue as Float
            invalidate()
        }
    }


    constructor(context: Context) : this(context, null)
    constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
            context,
            attrs,
            defStyleAttr
    ) {
        if (attrs != null) {
            context.obtainStyledAttributes(attrs, R.styleable.DashboardView).also {
                ringBackgroundWidth = it.getDimension(
                        R.styleable.DashboardView_ringBackgroundWidth,
                        ringBackgroundWidth
                )
                ringWidth = it.getDimension(R.styleable.DashboardView_ringWidth, ringWidth)
                ringBackgroundColor =
                        it.getColor(R.styleable.DashboardView_ringBackgroundColor, ringBackgroundColor)
                val ringStartColor =
                        it.getColor(R.styleable.DashboardView_ringStartColor, Color.GRAY)
                val ringEndColor = it.getColor(R.styleable.DashboardView_ringEndColor, Color.GREEN)
                ringColor = intArrayOf(ringStartColor, ringEndColor)
                calibrationColor =
                        it.getColor(R.styleable.DashboardView_calibrationColor, calibrationColor)
                calibrationWidth =
                        it.getDimension(R.styleable.DashboardView_calibrationWidth, calibrationWidth)
                calibrationLongWidth =
                        it.getDimension(R.styleable.DashboardView_calibrationLongWidth, calibrationLongWidth)
                calibrationCount =
                        it.getInt(R.styleable.DashboardView_calibrationCount, calibrationCount)
                calibrationLongLineWidth = it.getDimension(
                        R.styleable.DashboardView_calibrationLongLineWidth,
                        calibrationLongLineWidth
                )
                calibrationShortLineWidth = it.getDimension(
                        R.styleable.DashboardView_calibrationShortLineWidth,
                        calibrationShortLineWidth
                )
                calibrationMargin =
                        it.getDimension(R.styleable.DashboardView_calibrationMargin, calibrationMargin)
                ringMargin = it.getDimension(R.styleable.DashboardView_ringMargin, ringMargin)
                isCalibrationMoreLineInCircle = it.getBoolean(
                        R.styleable.DashboardView_isCalibrationMoreLineInCircle,
                        isCalibrationMoreLineInCircle
                )
                calibrationLongSpace =
                        it.getInt(R.styleable.DashboardView_calibrationLongSpace, calibrationLongSpace)
                unDrawAngle = it.getFloat(R.styleable.DashboardView_unDrawAngle, unDrawAngle)
                calibrationUnDrawAngle = it.getFloat(R.styleable.DashboardView_calibrationUnDrawAngle, calibrationUnDrawAngle)
                currentProgress =
                        it.getFloat(R.styleable.DashboardView_currentProgress, currentProgress)
                currentProgressRatio = it.getFloat(
                        R.styleable.DashboardView_currentProgressRatio,
                        currentProgress / 100f
                )
                canAnimChangeProgress = it.getBoolean(
                        R.styleable.DashboardView_canAnimChangeProgress,
                        canAnimChangeProgress
                )
                it.getDrawable(R.styleable.DashboardView_pointerBitmap)?.let { drawable ->
                    if (drawable is BitmapDrawable) {
                        pointerBitmap = drawable.bitmap
                    }
                }
                ringCap = it.getInt(R.styleable.DashboardView_ringCap, ringCap)
                endPointColor = it.getColor(R.styleable.DashboardView_endPointColor, endPointColor)
                endPointWidth =
                        it.getDimension(R.styleable.DashboardView_endPointWidth, endPointWidth)
                isDrawFirstCalibration = it.getBoolean(
                        R.styleable.DashboardView_isDrawFirstCalibration,
                        isDrawLastCalibration
                )
                isDrawLastCalibration = it.getBoolean(
                        R.styleable.DashboardView_isDrawLastCalibration,
                        isDrawLastCalibration
                )
                calibrationUnDrawCount = it.getInt(
                        R.styleable.DashboardView_calibrationUnDrawCount,
                        calibrationUnDrawCount
                )
            }.recycle()
        }
        isInint = false
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        val widthMode = MeasureSpec.getMode(widthMeasureSpec)
        val heightMode = MeasureSpec.getMode(heightMeasureSpec)
        val widthSize = MeasureSpec.getSize(widthMeasureSpec)
        val heightSize = MeasureSpec.getSize(heightMeasureSpec)
        if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(48 * 3, 48 * 3)
        }
//        logI("widthMode:${widthMode},heightMode:${heightMode}")
//        logI("widthSize:${widthSize},heightSize:${heightSize}")
//        when (widthMode) {
//            MeasureSpec.AT_MOST -> {
//                logE("AT_MOST")
//            }
//            MeasureSpec.EXACTLY  -> {
//                logE("EXACTLY")
//            }
//            MeasureSpec.UNSPECIFIED  -> {
//                logE("UNSPECIFIED")
//            }     
//        }
//        when (heightMode) {
//            MeasureSpec.AT_MOST -> {
//                logE("AT_MOST1")
//            }
//            MeasureSpec.EXACTLY  -> {
//                logE("EXACTLY1")
//            }
//            MeasureSpec.UNSPECIFIED  -> {
//                logE("UNSPECIFIED1")
//            }
//        }
    }

    override fun onDraw(canvas: Canvas?) {
        canvas ?: return
        val centerX = width / 2f
        val centerY = height / 2f
        drawCircle(canvas, centerX, centerY)
        drawCalibration(canvas, centerX, centerY)
        drawEndPoint(canvas, centerX, centerY)
        super.onDraw(canvas)
        drawZhizhen(canvas, centerX, centerY)
    }

    /**
     * 绘制指针
     */
    private fun drawZhizhen(canvas: Canvas, centerX: Float, centerY: Float) {
        val zhizhen = pointerBitmap ?: return
        canvas.save()
        //开始角度，绘制角度，90是把指针默认指向绘制扇形的0角度
        canvas.rotate(startAngle + _currentProgress + 90, centerX, centerY)
        canvas.drawBitmap(zhizhen, centerX - zhizhen.width / 2f, centerY - zhizhen.height, paint)
        canvas.restore()
    }

    /**
     * 绘制小圆点
     * 结束点的位置旋转到正上方
     */
    //根据圆环和圆环背景的宽度以及圆环的间隔确定小圆点的距离
    private val realCirclePadding: Float
        get() = max(ringBackgroundWidth, ringWidth) / 2 + ringMargin

    private fun drawEndPoint(canvas: Canvas, centerX: Float, centerY: Float) {
        if (endPointWidth <= 0f) {
            return
        }
        canvas.save()
        val oldStyle = paint.style

        //开始角度，绘制角度，90是把指针默认指向绘制扇形的0角度
        canvas.rotate(startAngle + _currentProgress + 90, centerX, centerY)
        paint.color = endPointColor
        paint.style = Paint.Style.FILL_AND_STROKE
        canvas.drawCircle(centerX, realCirclePadding, endPointWidth / 2, paint)
        canvas.restore()
        paint.style = oldStyle

    }


    /**
     * 绘制圆环
     */
    private val shaderMatrix = Matrix().apply {
        setRotate(90f)
    }
    private val circleRectF = RectF()
    private fun drawCircle(canvas: Canvas, centerX: Float, centerY: Float) {
        //绘制最外面背景
        paint.color = ringBackgroundColor
        paint.strokeWidth = ringBackgroundWidth
        val realCirclePadding = max(ringBackgroundWidth, ringWidth) / 2 + ringMargin
        circleRectF.set(
                realCirclePadding,
                realCirclePadding,
                centerX * 2 - realCirclePadding,
                centerY * 2 - realCirclePadding
        )
        canvas.drawArc(
                circleRectF,
                startAngle,
                360f - unDrawAngle,
                false,
                paint
        )
        //绘制当前刻度
        //刻度间隔
        paint.strokeWidth = ringWidth
        //设置渐变色
        paint.shader = SweepGradient(
                centerX,
                centerY,
                ringColor,
                null
        ).apply {
            shaderMatrix.setRotate(startAngle - 10, centerX, centerY)
            setLocalMatrix(shaderMatrix)
        }
        canvas.drawArc(
                circleRectF,
                startAngle,
                _currentProgress.coerceAtLeast(0.1f),
                false,
                paint
        )
        paint.shader = null
//        //绘制内部圆,造成为伪圆环效果
//        paint.color = Color.parseColor("#101010")
//        paint.strokeWidth = circleWidth
//        canvas.drawCircle(centerX, centerY, centerX - circleWidth, paint)
    }


    /**
     * 画刻度
     */
    private fun drawCalibration(canvas: Canvas, centerX: Float, centerY: Float) {
        canvas.save()
        //这里可以控制刻度线的绘制区域
        val unDrawAngle = calibrationUnDrawAngle
        val degrees = (360f - unDrawAngle) / calibrationCount
        canvas.rotate(-(90f - unDrawAngle / 2), centerX, centerY)
        //刻度线是从0开始，所以这里加一
        val allCount = calibrationCount + 1
        repeat(allCount) {
            val enableDraw = when (it) {
                0 -> {//是否绘制第一条
                    isDrawFirstCalibration
                }
                calibrationCount -> {//是否绘制最后一条
                    isDrawLastCalibration
                }
                else -> {//除去不绘制的线，都需要绘制
                    it < (calibrationCount - calibrationUnDrawCount)
                }
            }
            //第一个和最后一个不绘制
            if (enableDraw) {
                //设置画笔颜色
                if (it.toFloat() / allCount <= currentProgressRatio) {
                    paint.color = calibrationColor
                } else {
                    paint.color = getColorWithAlpha(0.4f, calibrationColor)
                }
                if (it % calibrationLongSpace == 0) {
                    //画长刻度线
                    paint.strokeWidth = calibrationLongWidth
                    canvas.drawLine(
                            calibrationMargin,
                            centerY,
                            calibrationMargin + calibrationLongLineWidth,
                            centerY,
                            paint
                    )
                } else {
                    //画短刻度线
                    paint.strokeWidth = calibrationWidth
                    val startX = if (isCalibrationMoreLineInCircle) calibrationMargin else {
                        calibrationMargin + calibrationLongLineWidth - calibrationShortLineWidth
                    }
                    canvas.drawLine(
                            startX,
                            centerY,
                            startX + calibrationShortLineWidth,
                            centerY,
                            paint
                    )
                }
            }
            canvas.rotate(degrees, centerX, centerY)
        }
        canvas.restore()
    }

    /**
     * 给color添加透明度
     * @param alpha 透明度 0f～1f
     * @param baseColor 基本颜色
     * @return
     */
    private fun getColorWithAlpha(alpha: Float, baseColor: Int): Int {
        val a = 255.coerceAtMost(0.coerceAtLeast((alpha * 255).toInt())) shl 24
        val rgb = 0x00ffffff and baseColor
        return a + rgb
    }

    private val startPointF = PointF()
    private val cenPointF = PointF()
    private val endPointF = PointF()
    private var _isMoveByFinger = false
    private var _canAnimChangeProgressTemp = canAnimChangeProgress
    override fun onTouchEvent(event: MotionEvent?): Boolean {
        if (onDragEndPointListener == null) {
            _isMoveByFinger = false
            return super.onTouchEvent(event)
        }
        event?.let {
            when (event.action) {
                MotionEvent.ACTION_DOWN -> {
                    cenPointF.y = height.toFloat() / 2
                    cenPointF.x = width.toFloat() / 2
                    getCirclePoint(startAngle.toDouble(), startPointF)
                    //获取当前结束小圆点的位置
                    val point = PointF()
                    getCirclePoint(_currentProgress.toDouble() + startAngle, point)
                    //为了用户体验
                    //超过矩形多少距离，仍然认为在矩形内，允许手指滑动
                    val fingerSlop = fingerBeyondTheBorder
                    val rect = RectF(point.x - endPointWidth / 2 - fingerSlop,
                            point.y - endPointWidth / 2 - fingerSlop,
                            point.x + endPointWidth / 2 + fingerSlop,
                            point.y + endPointWidth / 2 + fingerSlop)
                    //判断是否在原点范围内
                    if (rect.contains(event.x, event.y)) {
                        _isMoveByFinger = true
                        _canAnimChangeProgressTemp = canAnimChangeProgress
                        canAnimChangeProgress = false
                        reDraw(it)
                    }
                }

                MotionEvent.ACTION_MOVE -> {
                    if (_isMoveByFinger) {
                        reDraw(it)
                    }
                }
                MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
                    if (_isMoveByFinger) {
                        reDraw(it)
                    }
                    _isMoveByFinger = false
                    canAnimChangeProgress = _canAnimChangeProgressTemp
                }
            }
            parent.requestDisallowInterceptTouchEvent(_isMoveByFinger)
        }
        return true
    }

    private fun reDraw(event: MotionEvent) {
        this.endPointF.x = event.x
        this.endPointF.y = event.y
        val drawAngle = (360f - unDrawAngle)
        val angleRatio = getRotation(cenPointF, startPointF, this.endPointF).toFloat() / drawAngle
        if (abs(angleRatio - currentProgressRatio) > allowSlidingMaxRatio) {
            //为了更好的模拟滑动
            //变化值大于0.2，则本次滑动无效
            //这个判断可以去除，值也可以调整
            return
        }
        val newProgressRatio = angleRatio.coerceAtLeast(0f).coerceAtMost(1f)
        if (onDragEndPointListener?.invoke(newProgressRatio, event.action == MotionEvent.ACTION_UP) == true) {
            currentProgressRatio = newProgressRatio
        }
    }

    /**
     * 值总是大于0
     */
    private val pointTemp = PointF()
    private fun getRotation(cen: PointF, first: PointF, second: PointF): Double {
        pointTemp.set(0f, cen.y)
        val angle = when {
            first.y >= cen.y -> {
                //起点位于中心点下方
                val firstAngle = getAngle(cen, pointTemp, first)
                val secondAngle = getAngle(cen, pointTemp, second)
                if (second.y >= cen.y) {
                    if (second.x >= cen.x) {
                        firstAngle + (360 - secondAngle)
                    } else {
                        firstAngle - secondAngle
                    }

                } else {
                    firstAngle + secondAngle
                }
            }
            else -> {
                //起点位于中心点上方
                getAngle(cen, startPointF, second)
            }
        }
        return angle
    }

    private fun getAngle(cen: PointF, startPointF: PointF, second: PointF): Double {
        var cosFi: Double
        val dsx = startPointF.x - cen.x
        val dsy = startPointF.y - cen.y
        val dex = second.x - cen.x
        val dey = second.y - cen.y
        cosFi = (dsx * dex + dsy * dey).toDouble()
        val norm = ((dsx * dsx + dsy * dsy) * (dex * dex + dey * dey)).toDouble()
        cosFi /= sqrt(norm)
        if (cosFi >= 1.0) return 0.0
        if (cosFi <= -1.0) return Math.PI
        val fi = acos(cosFi)
        return Math.toDegrees(fi)
    }


    /**
     * 获取圆山某个点的坐标
     */
    private fun getCirclePoint(angle: Double, pointF: PointF) {
        val x = cenPointF.x
        val y = cenPointF.y
        val radius = x - realCirclePadding
        val pointX = x + radius * cos(Math.toRadians(angle))
        val pointY = y + radius * sin(Math.toRadians(angle))
        pointF.set(pointX.toFloat(), pointY.toFloat())
    }
}

